﻿using Microsoft.Office.Core;
using Microsoft.Office.InfoPath;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Xml;
using System.Xml.XPath;

namespace InfoPathHelpers.Submit
{
    /// <summary>
    /// Provides a number of methods to assist with common operations in InfoPath.
    /// </summary>
    public static class SubmitHelpers
    {
        /// <summary>
        /// Checks the supplied exception to see if it was thrown as the result of cancellation.
        /// </summary>
        /// <param name="exception">System.Net.WebExcetion thrown by an EmailSubmitConnection.Excecute().</param>
        /// <returns>A boolean value indicating whether the exception was thrown by cancellation or not.</returns>
        /// <exception cref="ArgumentNullException">exception is null.</exception>
        public static bool WasSubmitCancelled(System.Net.WebException exception)
        {
            if (exception == null) throw new ArgumentNullException("exception");
            return exception.Message.Contains("The form was not submitted because the action was canceled.")  || exception.Message.Contains("Operation aborted");
        }

        /// <summary>
        /// Safely executes an EmailSubmitConnection by removing existing temporary files prior to execution.
        /// </summary>
        /// <param name="emailSubmitConnection">The data connection you wish to execute (Must be of type EmailSubmitConnection).</param>
        /// <exception cref="ArgumentException">emailSubmitConnection is not of type EmailSubmitConnection.</exception>
        public static bool TryExecuteEmailSubmit(DataConnection emailSubmitConnection)
        {
            return TryExecuteEmailSubmit(emailSubmitConnection, true, null);
        }

        /// <summary>
        /// Safely executes an EmailSubmitConnection by removing existing temporary files prior to execution.
        /// </summary>
        /// <param name="formContext">A FormContext representing the host form to submit.</param>
        /// <param name="connectionName">The name of the connection to use for submission.</param>
        /// <exception cref="KeyNotFoundException">connectionName does not exist in formContext.DataConnection.</exception>
        /// <exception cref="ArgumentException">emailSubmitConnection is not of type EmailSubmitConnection.</exception>
        public static bool TryExecuteEmailSubmit(FormContext formContext, string connectionName)
        {
            return TryExecuteEmailSubmit(formContext, connectionName, true, null);
        }

        /// <summary>
        /// Safely executes an EmailSubmitConnection by removing existing temporary files prior to execution.
        /// With optional parameters to specify custom error messages.
        /// </summary>
        /// <param name="formContext">A FormContext representing the host form to submit.</param>
        /// <param name="connectionName">The name of the connection to use for submission.</param>
        /// <exception cref="KeyNotFoundException">connectionName does not exist in formContext.DataConnection.</exception>
        /// <exception cref="ArgumentException">emailSubmitConnection is not of type EmailSubmitConnection.</exception>
        public static bool TryExecuteEmailSubmit(FormContext formContext, string connectionName, bool showErrorMessages, string customErrorMessage)
        {
            // InfoPath is very clever and saves attachments with the chose name to "Temp" folder
            // before attaching to the email.
            // If you happen to have an identically named file in this location already it fails
            // with a generic "Can't add file as attachment" error.

            // As a workaround we could just delete this file but of course if it is in use say, 
            // because it's the form you ae trying to send you won't be able to delete it.
            // So the solution is to check if the form is in the temp files and save it somewhere else if it is.

            var Connection = formContext.DataConnections[connectionName] as EmailSubmitConnection;
            if (Connection == null)
                throw new ArgumentException("emailSubmitConnection must be of type EmailSubmitConnection");

            var AttachmentFile = GetAttachmentFileName(Connection);

            bool IsRenamed = false;

            // If the form is saved to the same place as the attachment will be saved.
            if(formContext.Uri.Equals(AttachmentFile.FullName, StringComparison.OrdinalIgnoreCase))
            {
                // Save to a random temporary file.
                string TempFileName = Path.Combine(Path.GetTempPath(),Path.GetRandomFileName() + ".xml");
                formContext.SaveAs(TempFileName);
                IsRenamed = true;
            }

            bool Result = TryExecuteEmailSubmit(Connection, showErrorMessages, customErrorMessage);

            if(IsRenamed)
            {
                // Save back to original location.
                formContext.SaveAs(AttachmentFile.FullName);
            }

            return Result;
        }

        /// <summary>
        /// DEPRECATED: Use the overload which takes FormContext and connection name.
        /// Safely executes an EmailSubmitConnection avoiding naming collisions.
        /// </summary>
        /// <param name="emailSubmitConnection">The data connection you wish to execute (Must be of type EmailSubmitConnection).</param>
        /// <exception cref="ArgumentException">emailSubmitConnection is not of type EmailSubmitConnection.</exception>
        public static bool TryExecuteEmailSubmit(DataConnection emailSubmitConnection, bool showErrorMessages, string customErrorMessage)
        {
            EmailSubmitConnection Connection = emailSubmitConnection as EmailSubmitConnection;
            if (Connection == null)
                throw new ArgumentException("emailSubmitConnection must be of type EmailSubmitConnection");

            // InfoPath is very clever and saves attachments with the chose name to "Temp" folder
            // before attaching to the email.
            // If you happen to have an identically named file in this location already it fails
            // with a generic "Can't add file as attachment" error.
            
            // As a workaround we could just delete this file but of course if it is in use say, 
            // because it's the form you ae trying to send you won't be able to delete it.
            // So the solution is to check if the form is in the temp files and save it somewhere else if it is.

            var AttachmentFile = GetAttachmentFileName(Connection);
            
            if (AttachmentFile.Exists)
            {
                try
                {
                    AttachmentFile.Delete();
                }
                catch(IOException)
                {
                    if (showErrorMessages)
                    {
                        var Message = customErrorMessage;
                        if (String.IsNullOrEmpty(Message))
                        {
                            Message = "A file with the same name as the attachment already exists and could not be deleted.\r\n\r\n" +
                                "Please make sure you do not have multiple copies of the for open and try again.";
                        }
                        MessageBox.Show(Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    return false;
                }
            }
            

            
            try
            {
                Connection.Execute();
                return true;
            }
            catch (WebException ex)
            {
                if (!WasSubmitCancelled(ex) && showErrorMessages)
                {
                    var Message = customErrorMessage;
                    if (String.IsNullOrEmpty(Message))
                    {
                        Message = ex.Message + "\n" + "Please check the form and try again!";
                    }
                    MessageBox.Show(Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                return false;
            }
        }

        /// <summary>
        /// DEPRECATED: Use the overload which takes FormContext and connection name.
        /// Safely executes an EmailSubmitConnection by removing existing temporary files prior to execution.
        /// </summary>
        /// <param name="emailSubmitConnection">The data connection you wish to execute (Must be of type EmailSubmitConnection).</param>
        /// <exception cref="ArgumentException">emailSubmitConnection is not of type EmailSubmitConnection.</exception>
        public static void ExecuteEmailSubmit(DataConnection emailSubmitConnection)
        {
            TryExecuteEmailSubmit(emailSubmitConnection, false, null);
        }

        private static FileInfo GetAttachmentFileName(EmailSubmitConnection connection)
        {
            var AttachmentFileName = connection.AttachmentFileName.ToString();
            var TemporaryFilesFolder = Path.GetTempPath();
            return new FileInfo(Path.Combine(TemporaryFilesFolder, AttachmentFileName));
        }
    }
}
